package ru.ifmo.thesis.solver.gp;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

import org.jgap.Chromosome;
import org.jgap.Configuration;
import org.jgap.FitnessFunction;
import org.jgap.Gene;
import org.jgap.Genotype;
import org.jgap.IChromosome;
import org.jgap.InvalidConfigurationException;
import org.jgap.Population;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ru.ifmo.thesis.domain.Job;
import ru.ifmo.thesis.domain.Shedule;
import ru.ifmo.thesis.domain.Worker;
import ru.ifmo.thesis.gp.MyConfiguration;
import ru.ifmo.thesis.gp.MyConfigurationOptions;
import ru.ifmo.thesis.gp.MyConfigurationOptionsBuilder;
import ru.ifmo.thesis.gp.MyFitnessFunction;
import ru.ifmo.thesis.gp.WorkPlanGene;
import ru.ifmo.thesis.solver.ISolver;
import ru.ifmo.thesis.task.Task;
import ru.ifmo.thesis.util.Algorithms;

public class GPSolver implements ISolver{
	
	private static final Logger logger = LoggerFactory.getLogger(GPSolver.class);

	private static final Random random = new Random();
	
	private Configuration conf;
	
	private final int ofPopulation;
	
	public Genotype population;
	
	private CompletionRule completionRule;
	
	private MyConfigurationOptions options;
	
	public GPSolver() {
		this(new FixedIterations(), new MyConfigurationOptionsBuilder().build());
	}
	
	public GPSolver(CompletionRule completionRule, MyConfigurationOptions options) {
		this.completionRule = completionRule;
		this.ofPopulation = options.getOfPopulation();
		this.options = options;
	}
	
	@Override
	public Shedule solve(Task t) {
		try {
			doSetup(t, ofPopulation); 
			return solveInternal(t);
		} catch (InvalidConfigurationException e) {
			throw new RuntimeException(e);
		}
	}

	private void doSetup(Task t, int ofPopulation2)
			throws InvalidConfigurationException {
		if (getConfiguration().getSampleChromosome() == null) {
			getConfiguration().setSampleChromosome(createRandomChromosome(
					getConfiguration(), t.getWorkers(), t.getJobs()));
		}
		
		List<Worker> workers = t.getWorkers();
		List<Job> tasks = t.getJobs();
		population = createPopulation(getConfiguration(), workers, tasks, ofPopulation2);
	}

	public Shedule solve(Task t, Shedule seed) {
		try {
			doSetup(t, ofPopulation - 1);
			population.getPopulation().addChromosome(toChromosome(seed));
			return solveInternal(t);
		} catch (InvalidConfigurationException e) {
			throw new RuntimeException(e);
		}
	}

	private IChromosome toChromosome(Shedule seed) throws InvalidConfigurationException {
		Gene[] myGenes = new Gene[seed.getWorkers().size()];

		Iterator<Worker> wi = seed.getWorkers().iterator();
		for (int i = 0; i < seed.getWorkers().size(); i++) {
			Worker current = wi.next();
			myGenes[i] = new WorkPlanGene(conf, current);
			for (Job j : seed.getPersonalShedule(current)) {
				@SuppressWarnings("unchecked")
				ArrayList<Job> allele = (ArrayList<Job>) myGenes[i].getAllele();
				allele.add(j);
			}
		}

		return new Chromosome(conf, myGenes);
	}

	private Shedule solveInternal(Task t) throws InvalidConfigurationException {
		while (!completionRule.isTaskCompleted(genotypeCopy())) {
			population.evolve();
		}
		
		population.getFittestChromosome();

		return Algorithms.constructShedule(population.getFittestChromosome());
	}

	/**
	 * Only reason for that is a side effect in GAP code. Motherfuckingly!
	 * 
	 * @return
	 * @throws InvalidConfigurationException
	 */
	private Genotype genotypeCopy() throws InvalidConfigurationException {
		Population popz = new  Population(getConfiguration());
		popz.addChromosomes(population.getPopulation());
		return new Genotype(getConfiguration(), popz);
	}

	private void setupConf() {
		conf = new MyConfiguration(options);
		FitnessFunction myFunc = new MyFitnessFunction();
		
		try {
			conf.setFitnessFunction(myFunc);
			conf.setPopulationSize(ofPopulation);
		} catch (InvalidConfigurationException e) {
			throw new RuntimeException(e);
		}
	}

	private Genotype createPopulation(Configuration conf, List<Worker> workers, List<Job> tasks, int populationSize) throws InvalidConfigurationException {
		Population population = new Population(conf);
		
		for (int i = 0; i < populationSize; i++) {
			population.addChromosome(createRandomChromosome(conf, workers, tasks));
		}
		
		return new Genotype(conf, population);
	}
	
	private IChromosome createRandomChromosome(Configuration conf,
			List<Worker> workers, List<Job> jobs) throws InvalidConfigurationException {
		
		Gene[] myGenes = new Gene[workers.size()];

		for (int i = 0; i < workers.size(); i++) {
			myGenes[i] = new WorkPlanGene(conf, workers.get(i));
		}

		List<Job> nJobs = new ArrayList<Job>(jobs);
		
		Collections.shuffle(nJobs);
		for (Job j : nJobs) {
			int rndWorkerId = random.nextInt(myGenes.length);
			
			@SuppressWarnings("unchecked")
			ArrayList<Job> allele = (ArrayList<Job>) myGenes[rndWorkerId].getAllele();

			allele.add(j);
		}
		
		return new Chromosome(conf, myGenes);
	}
	
	public Configuration getConfiguration() {
		if (conf == null) {
			setupConf();
		}
		return conf;
	}
	
	public void reset() {
		Configuration.reset();
		this.conf = null;
		this.completionRule = completionRule.newCopy();
	}
	
	public Shedule bestSolutionSoFar() {
		return Algorithms.constructShedule(population.getFittestChromosome());
	}
	
	public List<Shedule> top(int numberOfChromosomes) {
		List<Shedule> result = new ArrayList<Shedule>();
		
		for (Object o : population.getFittestChromosomes(numberOfChromosomes)) {
			result.add(Algorithms.constructShedule((IChromosome) o));
		}
		
		return result;
	}
	
	public List<Shedule> allChromosomes() {
		return top(population.getPopulation().size());
	}

	
	/**
	 * Use after calling reset method
	 * @param options
	 */
	public void setOptions(MyConfigurationOptions options) {
		this.options = options;
	}
}
